/*-------------------------------------------------------------------------
 *
 * latch.c
 *	  进程间闩的例程
 *
 * poll() 实现使用所谓的自管道技巧来克服与 poll() 和在信号处理程序中
 * 设置全局标志有关的竞争条件。当设置了一个闩且当前进程在等待它时，
 * 信号处理程序通过向管道写入一个字节来唤醒 WaitLatch 中的 poll()。
 * 仅通过信号本身不会在所有平台上中断 poll()，甚至在能够中断的平
 * 台上，发生在 poll() 调用之前的信号不会阻止 poll() 进入睡眠。尽管如
 * 此，来自管道的字节确实会可靠地中断睡眠，并导致 poll() 立即返回，即
 * 使信号在 poll() 开始之前到达。
 *
 * epoll() 实现通过另一种技术克服了竞争：它保持 SIGURG 被阻塞，并从
 * signalfd() 描述符中消费。我们无需注册信号处理程序或创建自己的自管
 * 道。我们假设任何支持 Linux epoll() 的系统也支持 Linux signalfd()。
 *
 * kqueue() 实现使用 EVFILT_SIGNAL 来等待 SIGURG。
 *
 * Windows 实现使用所有主进程子进程继承的 Windows 事件。在那里不需要
 * 自管道技巧。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/storage/ipc/latch.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <unistd.h>
#ifdef HAVE_SYS_EPOLL_H
#include <sys/epoll.h>
#endif
#ifdef HAVE_SYS_EVENT_H
#include <sys/event.h>
#endif
#ifdef HAVE_SYS_SIGNALFD_H
#include <sys/signalfd.h>
#endif
#ifdef HAVE_POLL_H
#include <poll.h>
#endif

#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "port/atomics.h"
#include "portability/instr_time.h"
#include "postmaster/postmaster.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
#include "storage/shmem.h"
#include "utils/memutils.h"

/*
 * 选择要使用的 fd 准备原语。通常，将使用 OS 支持的“最现代”的
 * 原语，但为了测试，可以手动指定使用的原语。如果愿意，只需在此块之前某处添加一个定义。
 */
#if defined(WAIT_USE_EPOLL) || defined(WAIT_USE_POLL) || \
	defined(WAIT_USE_KQUEUE) || defined(WAIT_USE_WIN32)
/* 不要覆盖手动选择 */
#elif defined(HAVE_SYS_EPOLL_H)
#define WAIT_USE_EPOLL
#elif defined(HAVE_KQUEUE)
#define WAIT_USE_KQUEUE
#elif defined(HAVE_POLL)
#define WAIT_USE_POLL
#elif WIN32
#define WAIT_USE_WIN32
#else
#error "no wait set implementation available"
#endif

/*
 * 默认情况下，我们使用带有 poll() 的自管道和带有 epoll() 的 signalfd（如果可用）。
 * 基于问题报告，我们暂时避免在 illumos 上使用 signalfd。
 * 对于测试，选择也可以手动指定。
 */
#if defined(WAIT_USE_POLL) || defined(WAIT_USE_EPOLL)
#if defined(WAIT_USE_SELF_PIPE) || defined(WAIT_USE_SIGNALFD)
/* 不要覆盖手动选择 */
#elif defined(WAIT_USE_EPOLL) && defined(HAVE_SYS_SIGNALFD_H) && \
	!defined(__illumos__)
#define WAIT_USE_SIGNALFD
#else
#define WAIT_USE_SELF_PIPE
#endif
#endif

/* latch.h 中的 typedef */
struct WaitEventSet
{
	int			nevents;		/* 注册事件的数量 */
	int			nevents_space;	/* 此集合中事件的最大数量 */

	/*
	 * 事件数组，长度为 nevents_space，存储此集合正在等待的事件定义。
	 */
	WaitEvent  *events;

	/*
	 * 如果在任何等待事件中指定了 WL_LATCH_SET，latch 是指向
	 * 所述闩锁的指针，latch_pos 是 ->events 数组中的偏移量。这是
	 * 有用的，因为我们在执行与等待相关的系统调用之前检查
	 * 闩锁的状态。
	 */
	Latch	   *latch;
	int			latch_pos;

	/*
	 * WL_EXIT_ON_PM_DEATH 被转换为 WL_POSTMASTER_DEATH，但是此标志
	 * 被设置以便在检测到主进程死亡时立即退出，而不是返回。
	 */
	bool		exit_on_postmaster_death;

#if defined(WAIT_USE_EPOLL)
	int			epoll_fd;
	/* epoll_wait 在用户提供的数组中返回事件，只需分配一次 */
	struct epoll_event *epoll_ret_events;
#elif defined(WAIT_USE_KQUEUE)
	int			kqueue_fd;
	/* kevent 在用户提供的数组中返回事件，只需分配一次 */
	struct kevent *kqueue_ret_events;
	bool		report_postmaster_not_running;
#elif defined(WAIT_USE_POLL)
	/* poll 每次 poll() 调用时期望等待的事件，只需准备一次 */
	struct pollfd *pollfds;
#elif defined(WAIT_USE_WIN32)

	/*
	 * Windows 事件数组。第一个元素始终包含
	 * pgwin32_signal_event，因此剩余元素偏移一个（即
	 * event->pos + 1）。
	 */
	HANDLE	   *handles;
#endif
};

/* 一个常用的 WaitEventSet，用于实现 WatchLatch() */
static WaitEventSet *LatchWaitSet;

/* 闩锁在 LatchWaitSet 中的位置。 */
#define LatchWaitSetLatchPos 0

#ifndef WIN32
/* 我们当前在 WaitLatch 中吗？信号处理程序想知道。 */
static volatile sig_atomic_t waiting = false;
#endif

#ifdef WAIT_USE_SIGNALFD
/* 在 Linux 上，我们将通过 signalfd 文件描述符接收 SIGURG。 */
static int	signal_fd = -1;
#endif

#ifdef WAIT_USE_SELF_PIPE
/* 自管道的读写端 */
static int	selfpipe_readfd = -1;
static int	selfpipe_writefd = -1;

/* 拥有自管道的进程 --- 需要检查目的 */
static int	selfpipe_owner_pid = 0;

/* 私有函数原型 */
static void fc_latch_sigurg_handler(SIGNAL_ARGS);
static void fc_sendSelfPipeByte(void);
#endif

#if defined(WAIT_USE_SELF_PIPE) || defined(WAIT_USE_SIGNALFD)
static void fc_drain(void);
#endif

#if defined(WAIT_USE_EPOLL)
static void fc_WaitEventAdjustEpoll(WaitEventSet *fc_set, WaitEvent *fc_event, int fc_action);
#elif defined(WAIT_USE_KQUEUE)
static void fc_WaitEventAdjustKqueue(WaitEventSet *fc_set, WaitEvent *fc_event, int fc_old_events);
#elif defined(WAIT_USE_POLL)
static void fc_WaitEventAdjustPoll(WaitEventSet *fc_set, WaitEvent *fc_event);
#elif defined(WAIT_USE_WIN32)
static void fc_WaitEventAdjustWin32(WaitEventSet *fc_set, WaitEvent *fc_event);
#endif

static inline int fc_WaitEventSetWaitBlock(WaitEventSet *fc_set, int fc_cur_timeout,
										WaitEvent *fc_occurred_events, int fc_nevents);

/*
 * 初始化进程本地闩锁基础设施。
 *
 * 这必须在任何可以等待
 * 闩锁的进程启动期间被调用，且在发送任何 InitLatch() 或 OwnLatch() 调用之前。
 */
void InitializeLatchSupport(void)
{
#if defined(WAIT_USE_SELF_PIPE)
	int			fc_pipefd[2];

	if (IsUnderPostmaster)
	{
		/*
		 * 我们可能继承了由 postmaster 创建的自管道连接。当然，子进程必须创建自己的自管道，我们确实希望它们关闭继承的文件描述符以确保安全。
		 */
		if (selfpipe_owner_pid != 0)
		{
			/* 断言我们在子进程中只会经过这里一次 */
			Assert(selfpipe_owner_pid != MyProcPid);
			/* 释放 postmaster 的管道文件描述符；忽略任何错误 */
			(void) close(selfpipe_readfd);
			(void) close(selfpipe_writefd);
			/* 清理，仅仅出于安全考虑；我们将在下面设置这些 */
			selfpipe_readfd = selfpipe_writefd = -1;
			selfpipe_owner_pid = 0;
			/* 保持 fd.c 的账目是正确的 */
			ReleaseExternalFD();
			ReleaseExternalFD();
		}
		else
		{
			/*
			 * postmaster 没有创建自管道……或者我们在 EXEC_BACKEND 构建中，此时无所谓，因为 postmaster 的管道文件描述符因 FD_CLOEXEC 的作用而关闭。 
			 * fd.c 也不会有状态需要清理。
			 */
			Assert(selfpipe_readfd == -1);
		}
	}
	else
	{
		/* 在 postmaster 或独立后端中，断言我们只会做一次 */
		Assert(selfpipe_readfd == -1);
		Assert(selfpipe_owner_pid == 0);
	}

	/*
	 * 设置自管道，允许信号处理程序唤醒 WaitLatch 中的 poll()/epoll_wait()。使写入端为非阻塞，这样如果事件已经多次设置填满内核缓冲区，SetLatch 就不会阻塞。使读取端也为非阻塞，以便我们可以通过读取直到 EAGAIN 或 EWOULDBLOCK 来轻松清除管道。
	 * 此外，使两个文件描述符都在执行时关闭，因为我们当然不希望任何子进程干扰它们。
	 */
	if (pipe(fc_pipefd) < 0)
		elog(FATAL, "pipe() failed: %m");
	if (fcntl(fc_pipefd[0], F_SETFL, O_NONBLOCK) == -1)
		elog(FATAL, "fcntl(F_SETFL) failed on read-end of self-pipe: %m");
	if (fcntl(fc_pipefd[1], F_SETFL, O_NONBLOCK) == -1)
		elog(FATAL, "fcntl(F_SETFL) failed on write-end of self-pipe: %m");
	if (fcntl(fc_pipefd[0], F_SETFD, FD_CLOEXEC) == -1)
		elog(FATAL, "fcntl(F_SETFD) failed on read-end of self-pipe: %m");
	if (fcntl(fc_pipefd[1], F_SETFD, FD_CLOEXEC) == -1)
		elog(FATAL, "fcntl(F_SETFD) failed on write-end of self-pipe: %m");

	selfpipe_readfd = fc_pipefd[0];
	selfpipe_writefd = fc_pipefd[1];
	selfpipe_owner_pid = MyProcPid;

	/* 告诉 fd.c 关于这两个长期存在的文件描述符 */
	ReserveExternalFD();
	ReserveExternalFD();

	pqsignal(SIGURG, fc_latch_sigurg_handler);
#endif

#ifdef WAIT_USE_SIGNALFD
	sigset_t	fc_signalfd_mask;

	/* 阻塞 SIGURG，因为我们将通过 signalfd 接收它。 */
	sigaddset(&UnBlockSig, SIGURG);

	/* 设置 signalfd 以接收 SIGURG 通知。 */
	sigemptyset(&fc_signalfd_mask);
	sigaddset(&fc_signalfd_mask, SIGURG);
	signal_fd = signalfd(-1, &fc_signalfd_mask, SFD_NONBLOCK | SFD_CLOEXEC);
	if (signal_fd < 0)
		elog(FATAL, "signalfd() failed");
	ReserveExternalFD();
#endif

#ifdef WAIT_USE_KQUEUE
	/* 忽略 SIGURG，因为我们将通过 kqueue 接收它。 */
	pqsignal(SIGURG, SIG_IGN);
#endif
}

void InitializeLatchWaitSet(void)
{
	int			fc_latch_pos PG_USED_FOR_ASSERTS_ONLY;

	Assert(LatchWaitSet == NULL);

	/* 设置 WaitLatch() 使用的 WaitEventSet。 */
	LatchWaitSet = CreateWaitEventSet(TopMemoryContext, 2);
	fc_latch_pos = AddWaitEventToSet(LatchWaitSet, WL_LATCH_SET, PGINVALID_SOCKET,
								  MyLatch, NULL);
	if (IsUnderPostmaster)
		AddWaitEventToSet(LatchWaitSet, WL_EXIT_ON_PM_DEATH,
						  PGINVALID_SOCKET, NULL, NULL);

	Assert(fc_latch_pos == LatchWaitSetLatchPos);
}

void ShutdownLatchSupport(void)
{
#if defined(WAIT_USE_POLL)
	pqsignal(SIGURG, SIG_IGN);
#endif

	if (LatchWaitSet)
	{
		FreeWaitEventSet(LatchWaitSet);
		LatchWaitSet = NULL;
	}

#if defined(WAIT_USE_SELF_PIPE)
	close(selfpipe_readfd);
	close(selfpipe_writefd);
	selfpipe_readfd = -1;
	selfpipe_writefd = -1;
	selfpipe_owner_pid = InvalidPid;
#endif

#if defined(WAIT_USE_SIGNALFD)
	close(signal_fd);
	signal_fd = -1;
#endif
}

/*
 * 初始化一个进程本地的闩。
 */
void InitLatch(Latch *fc_latch)
{
	fc_latch->is_set = false;
	fc_latch->maybe_sleeping = false;
	fc_latch->owner_pid = MyProcPid;
	fc_latch->is_shared = false;

#if defined(WAIT_USE_SELF_PIPE)
	/* 断言在这个进程中已经调用了 InitializeLatchSupport */
	Assert(selfpipe_readfd >= 0 && selfpipe_owner_pid == MyProcPid);
#elif defined(WAIT_USE_SIGNALFD)
	/* 断言在这个进程中已经调用了 InitializeLatchSupport */
	Assert(signal_fd >= 0);
#elif defined(WAIT_USE_WIN32)
	fc_latch->event = CreateEvent(NULL, TRUE, FALSE, NULL);
	if (fc_latch->event == NULL)
		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
#endif							/* WIN32 */
}

/*
 * 初始化一个可以从其他进程设置的共享闩。该闩最初无人拥有；使用 OwnLatch 将其与当前进程关联。
 *
 * 在为子进程分叉之前，必须在 postmaster 中调用 InitSharedLatch，通常是在使用 ShmemInitStruct 分配包含闩的共享内存块之后。（Unix 实现实际上不需要这样，但是 Windows 实现需要。）由于这一限制，我们在这里没有并发问题需要担心。
 *
 * 注意，本模块创建的其他句柄从未标记为可继承的。因此，我们不需要担心清理子进程对 postmaster 私有的闩或 WaitEventSets 的引用。
 */
void InitSharedLatch(Latch *fc_latch)
{
#ifdef WIN32
	SECURITY_ATTRIBUTES sa;

	/*
	 * 设置安全属性以指定事件是可继承的。
	 */
	ZeroMemory(&sa, sizeof(sa));
	sa.nLength = sizeof(sa);
	sa.bInheritHandle = TRUE;

	fc_latch->event = CreateEvent(&sa, TRUE, FALSE, NULL);
	if (fc_latch->event == NULL)
		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
#endif

	fc_latch->is_set = false;
	fc_latch->maybe_sleeping = false;
	fc_latch->owner_pid = 0;
	fc_latch->is_shared = true;
}

/*
 * 将共享闩与当前进程关联，使其可以等待该闩。
 *
 * 尽管有一个阈值检查以确保闩已被拥有，但我们在这里没有执行任何类型的锁定，这意味着如果两个进程试图同时拥有相同的闩，我们可能无法检测到错误。如果有任何这种风险，调用者必须提供一个互锁以防止这种情况。
 */
void OwnLatch(Latch *fc_latch)
{
	int			fc_owner_pid;

	/* 合理性检查 */
	Assert(fc_latch->is_shared);

#if defined(WAIT_USE_SELF_PIPE)
	/* 断言在这个进程中已经调用了 InitializeLatchSupport */
	Assert(selfpipe_readfd >= 0 && selfpipe_owner_pid == MyProcPid);
#elif defined(WAIT_USE_SIGNALFD)
	/* 断言在这个进程中已经调用了 InitializeLatchSupport */
	Assert(signal_fd >= 0);
#endif

	fc_owner_pid = fc_latch->owner_pid;
	if (fc_owner_pid != 0)
		elog(PANIC, "latch already owned by PID %d", fc_owner_pid);

	fc_latch->owner_pid = MyProcPid;
}

/*
 * 放弃当前进程拥有的共享闩。
 */
void DisownLatch(Latch *fc_latch)
{
	Assert(fc_latch->is_shared);
	Assert(fc_latch->owner_pid == MyProcPid);

	fc_latch->owner_pid = 0;
}

/*
 * 等待给定的闩被设置，或等待 postmaster 死亡，或直到超时。 'wakeEvents' 是一个位掩码，指定要等待的事件。如果闩已经设置（并且给出 WL_LATCH_SET），该函数将立即返回。
 *
 * “超时”以毫秒为单位给出。如果给出 WL_TIMEOUT 标志，则必须 >= 0。尽管它被声明为“长整型”，但实际上我们不支持超过 INT_MAX 毫秒的超时。请注意，当给出 WL_TIMEOUT 时会产生一些额外的开销，因此尽可能避免使用超时。
 *
 * 该闩必须由当前进程拥有，即它必须是用 InitLatch 初始化的进程本地闩，或者是通过调用 OwnLatch 与当前进程相关联的共享闩。
 *
 * 返回位掩码，指示造成唤醒的条件。注意，如果多个唤醒条件为真，则不保证我们在一次调用中返回所有条件，但我们至少会返回一个。
 */
int WaitLatch(Latch *fc_latch, int fc_wakeEvents, long fc_timeout,
		  uint32 fc_wait_event_info)
{
	WaitEvent	fc_event;

	/* 由 postmaster 管理的调用者必须以某种方式处理 postmaster 死亡。 */
	Assert(!IsUnderPostmaster ||
		   (fc_wakeEvents & WL_EXIT_ON_PM_DEATH) ||
		   (fc_wakeEvents & WL_POSTMASTER_DEATH));

	/*
	 * 一些调用者可能有除了 MyLatch 之外的其他锁，或者根本没有锁，或者希望以不同方式处理后端死亡。分配这些是便宜的，所以每次都这样做。
	 */
	if (!(fc_wakeEvents & WL_LATCH_SET))
		fc_latch = NULL;
	ModifyWaitEvent(LatchWaitSet, LatchWaitSetLatchPos, WL_LATCH_SET, fc_latch);
	LatchWaitSet->exit_on_postmaster_death =
		((fc_wakeEvents & WL_EXIT_ON_PM_DEATH) != 0);

	if (WaitEventSetWait(LatchWaitSet,
						 (fc_wakeEvents & WL_TIMEOUT) ? fc_timeout : -1,
						 &fc_event, 1,
						 fc_wait_event_info) == 0)
		return WL_TIMEOUT;
	else
		return fc_event.events;
}

/*
 * 类似于 WaitLatch，但有一个额外的套接字参数用于 WL_SOCKET_* 条件。
 *
 * 当等待一个套接字时，EOF 和错误条件总是导致套接字被报告为可读/可写/已连接，以便调用者可以处理这种条件。
 *
 * wakeEvents 必须包括 WL_EXIT_ON_PM_DEATH，用于在后端死亡时自动退出，或者 WL_POSTMASTER_DEATH，这是在返回值中设置的标志，如果后端死亡。后者对于需要其他行为而非立即退出的少数情况很有用。
 *
 * 注意：如今这只是对 WaitEventSet API 的包装。当频繁使用锁时，考虑创建一个存活时间更长的 WaitEventSet；这样更高效。
 */
int WaitLatchOrSocket(Latch *fc_latch, int fc_wakeEvents, pgsocket fc_sock,
				  long fc_timeout, uint32 fc_wait_event_info)
{
	int			fc_ret = 0;
	int			fc_rc;
	WaitEvent	fc_event;
	WaitEventSet *fc_set = CreateWaitEventSet(CurrentMemoryContext, 3);

	PG_TRY();
	{
		if (fc_wakeEvents & WL_TIMEOUT)
			Assert(fc_timeout >= 0);
		else
			fc_timeout = -1;

		if (fc_wakeEvents & WL_LATCH_SET)
			AddWaitEventToSet(fc_set, WL_LATCH_SET, PGINVALID_SOCKET,
							  fc_latch, NULL);

		/* 由 postmaster 管理的调用者必须以某种方式处理 postmaster 死亡。 */
		Assert(!IsUnderPostmaster ||
			   (fc_wakeEvents & WL_EXIT_ON_PM_DEATH) ||
			   (fc_wakeEvents & WL_POSTMASTER_DEATH));

		if ((fc_wakeEvents & WL_POSTMASTER_DEATH) && IsUnderPostmaster)
			AddWaitEventToSet(fc_set, WL_POSTMASTER_DEATH, PGINVALID_SOCKET,
							  NULL, NULL);

		if ((fc_wakeEvents & WL_EXIT_ON_PM_DEATH) && IsUnderPostmaster)
			AddWaitEventToSet(fc_set, WL_EXIT_ON_PM_DEATH, PGINVALID_SOCKET,
							  NULL, NULL);

		if (fc_wakeEvents & WL_SOCKET_MASK)
		{
			int			fc_ev;

			fc_ev = fc_wakeEvents & WL_SOCKET_MASK;
			AddWaitEventToSet(fc_set, fc_ev, fc_sock, NULL, NULL);
		}

		fc_rc = WaitEventSetWait(fc_set, fc_timeout, &fc_event, 1, fc_wait_event_info);

		if (fc_rc == 0)
			fc_ret |= WL_TIMEOUT;
		else
		{
			fc_ret |= fc_event.events & (WL_LATCH_SET |
								   WL_POSTMASTER_DEATH |
								   WL_SOCKET_MASK);
		}
	}
	PG_FINALLY();
	{
		FreeWaitEventSet(fc_set);
	}
	PG_END_TRY();

	return fc_ret;
}

/*
 * 设置一个锁并唤醒等待它的任何人。
 *
 * 如果锁已经设置，这很便宜，否则就不那么便宜。
 *
 * 注意：在信号处理程序中调用时，一定要在周围保存和恢复 errno。 （这在大多数信号处理程序中是标准做法，但我们曾经在仅设置标志的处理程序中省略它。）
 *
 * 注意：此函数从临界区和信号处理程序中调用，因此抛出错误不是一个好主意。
 */
void SetLatch(Latch *fc_latch)
{
#ifndef WIN32
	pid_t		fc_owner_pid;
#else
	HANDLE		fc_handle;
#endif

	/*
	 * 必须在这里放置内存屏障，以确保此进程可能更改的任何标志变量已经刷新到主内存，然后再检查/设置 is_set。
	 */
	pg_memory_barrier();

	/* 如果已经设置则快速退出 */
	if (fc_latch->is_set)
		return;

	fc_latch->is_set = true;

	pg_memory_barrier();
	if (!fc_latch->maybe_sleeping)
		return;

#ifndef WIN32

	/*
	 * 查看是否有任何人正在等待闩锁。如果我们在信号处理程序中，它可以是当前进程。
	 * 我们使用自管道或 SIGURG 向自己发送信号，以在这种情况下唤醒 WaitEventSetWaitBlock()，从而避免竞争。如果是其他进程，则发送信号。
	 *
	 * 只获取 owner_pid 一次，以防闩锁同时被拥有或解除拥有。XXX：这假设 pid_t 是原子的，这并不保证！实际上，pid_t 的有效范围适合在 32 位整数中，因此应该是原子的。在最坏的情况下，我们可能会信号错误的进程。即便如此，如果存在具有该虚假 pid 的进程并且属于 Postgres，你也非常倒霉；而 PG 数据库进程应该能够处理多余的 SIGUSR1 中断而不会出现问题。
	 *
	 * 这里可能的另一种竞争条件是一个新进程在我们查看后立即拥有闩锁，因此我们没有对其发送信号。这没问题，只要所有 ResetLatch/WaitLatch 的调用者遵循标准编码约定，在它们的循环底部等待，而不是顶部，这样它们就能正确处理在进入循环之前发生的闩锁设置事件。
	 */
	fc_owner_pid = fc_latch->owner_pid;
	if (fc_owner_pid == 0)
		return;
	else if (fc_owner_pid == MyProcPid)
	{
#if defined(WAIT_USE_SELF_PIPE)
		if (waiting)
			fc_sendSelfPipeByte();
#else
		if (waiting)
			kill(MyProcPid, SIGURG);
#endif
	}
	else
		kill(fc_owner_pid, SIGURG);

#else

	/*
	 * 查看是否有任何人正在等待闩锁。如果我们在信号处理程序中，它可以是当前进程。
	 *
	 * 在这里使用一个局部变量，以防有人并发更改事件字段（这实际不应发生）。
	 */
	fc_handle = fc_latch->event;
	if (fc_handle)
	{
		SetEvent(fc_handle);

		/*
		 * 注意，我们默默地忽略任何错误。我们可能在信号处理程序中或其他关键路径中，调用 elog() 是不安全的。
		 */
	}
#endif
}

/*
 * 清除闩锁。在此之后调用 WaitLatch 将会阻塞，除非在 WaitLatch 调用之前，闩锁再次被设置。
 */
void ResetLatch(Latch *fc_latch)
{
	/* 只有拥有者应该重置闸门 */
	Assert(fc_latch->owner_pid == MyProcPid);
	Assert(fc_latch->maybe_sleeping == false);

	fc_latch->is_set = false;

	/*
	 * 确保对 is_set 的写入在我们检查任何标志变量之前被刷新到主内存。否则，一个并发的 SetLatch 可能会错误地得出结论认为它不需要通知我们，即使我们已错过了一些 SetLatch 应该通知我们的标志更新。
	 */
	pg_memory_barrier();
}

/*
 * 创建一个 WaitEventSet，为 nevents 个不同的事件预留空间以进行等待。
 *
 * 然后可以高效地一起等待这些事件，使用 WaitEventSetWait()。
 */
WaitEventSet *
CreateWaitEventSet(MemoryContext fc_context, int fc_nevents)
{
	WaitEventSet *fc_set;
	char	   *fc_data;
	Size		fc_sz = 0;

	/*
	 * 使用 MAXALIGN 大小/对齐来保证后续内存使用的正确对齐。例如，在某些平台上，epoll_event 可能需要 8 字节对齐，但早期的分配如 WaitEventSet 和 WaitEvent 可能并没有大小来保证当纯粹使用 sizeof() 时的对齐。
	 */
	fc_sz += MAXALIGN(sizeof(WaitEventSet));
	fc_sz += MAXALIGN(sizeof(WaitEvent) * fc_nevents);

#if defined(WAIT_USE_EPOLL)
	fc_sz += MAXALIGN(sizeof(struct epoll_event) * fc_nevents);
#elif defined(WAIT_USE_KQUEUE)
	fc_sz += MAXALIGN(sizeof(struct kevent) * fc_nevents);
#elif defined(WAIT_USE_POLL)
	fc_sz += MAXALIGN(sizeof(struct pollfd) * fc_nevents);
#elif defined(WAIT_USE_WIN32)
	/* 需要为 pgwin32_signal_event 预留空间 */
	fc_sz += MAXALIGN(sizeof(HANDLE) * (fc_nevents + 1));
#endif

	fc_data = (char *) MemoryContextAllocZero(fc_context, fc_sz);

	fc_set = (WaitEventSet *) fc_data;
	fc_data += MAXALIGN(sizeof(WaitEventSet));

	fc_set->events = (WaitEvent *) fc_data;
	fc_data += MAXALIGN(sizeof(WaitEvent) * fc_nevents);

#if defined(WAIT_USE_EPOLL)
	fc_set->epoll_ret_events = (struct epoll_event *) fc_data;
	fc_data += MAXALIGN(sizeof(struct epoll_event) * fc_nevents);
#elif defined(WAIT_USE_KQUEUE)
	fc_set->kqueue_ret_events = (struct kevent *) fc_data;
	fc_data += MAXALIGN(sizeof(struct kevent) * fc_nevents);
#elif defined(WAIT_USE_POLL)
	fc_set->pollfds = (struct pollfd *) fc_data;
	fc_data += MAXALIGN(sizeof(struct pollfd) * fc_nevents);
#elif defined(WAIT_USE_WIN32)
	fc_set->handles = (HANDLE) fc_data;
	fc_data += MAXALIGN(sizeof(HANDLE) * fc_nevents);
#endif

	fc_set->latch = NULL;
	fc_set->nevents_space = fc_nevents;
	fc_set->exit_on_postmaster_death = false;

#if defined(WAIT_USE_EPOLL)
	if (!AcquireExternalFD())
	{
		/* 将其视为 epoll_create1 本身返回 EMFILE */
		elog(ERROR, "epoll_create1 failed: %m");
	}
	fc_set->epoll_fd = epoll_create1(EPOLL_CLOEXEC);
	if (fc_set->epoll_fd < 0)
	{
		ReleaseExternalFD();
		elog(ERROR, "epoll_create1 failed: %m");
	}
#elif defined(WAIT_USE_KQUEUE)
	if (!AcquireExternalFD())
	{
		/* 将其视为 kqueue 本身返回 EMFILE */
		elog(ERROR, "kqueue failed: %m");
	}
	fc_set->kqueue_fd = kqueue();
	if (fc_set->kqueue_fd < 0)
	{
		ReleaseExternalFD();
		elog(ERROR, "kqueue failed: %m");
	}
	if (fcntl(fc_set->kqueue_fd, F_SETFD, FD_CLOEXEC) == -1)
	{
		int			save_errno = errno;

		close(fc_set->kqueue_fd);
		ReleaseExternalFD();
		errno = save_errno;
		elog(ERROR, "fcntl(F_SETFD) failed on kqueue descriptor: %m");
	}
	fc_set->report_postmaster_not_running = false;
#elif defined(WAIT_USE_WIN32)

	/*
	 * 为了在等待时处理信号，我们需要添加一个特定于 win32 的事件。
	 * 我们在此例程的开头考虑到了额外的事件。有关更多详细信息，请参见 port/win32/signal.c。
	 *
	 * 注意：pgwin32_signal_event 应该优先，以确保在设置多个事件时它会被报告。我们希望确保挂起的信号得到服务。
	 */
	fc_set->handles[0] = pgwin32_signal_event;
	StaticAssertStmt(WSA_INVALID_EVENT == NULL, "");
#endif

	return fc_set;
}

/*
 * 释放先前创建的 WaitEventSet。
 *
 * 注意：最好是，这不应该释放任何可能在 exec() 之间继承的资源。如果释放了，我们可能会在许多场景中泄漏这些资源。对于 epoll 的情况，我们通过在创建 FD 时设置 EPOLL_CLOEXEC 来确保这一点。对于 Windows 的情况，我们假设所涉及的句柄是不可继承的。
 */
void FreeWaitEventSet(WaitEventSet *fc_set)
{
#if defined(WAIT_USE_EPOLL)
	close(fc_set->epoll_fd);
	ReleaseExternalFD();
#elif defined(WAIT_USE_KQUEUE)
	close(fc_set->kqueue_fd);
	ReleaseExternalFD();
#elif defined(WAIT_USE_WIN32)
	WaitEvent  *fc_cur_event;

	for (fc_cur_event = fc_set->events;
		 fc_cur_event < (fc_set->events + fc_set->nevents);
		 fc_cur_event++)
	{
		if (fc_cur_event->events & WL_LATCH_SET)
		{
			/* 使用闸门的 HANDLE */
		}
		else if (fc_cur_event->events & WL_POSTMASTER_DEATH)
		{
			/* 使用 PostmasterHandle */
		}
		else
		{
			/* 清理我们为套接字创建的事件对象 */
			WSAEventSelect(fc_cur_event->fd, NULL, 0);
			WSACloseEvent(fc_set->handles[fc_cur_event->pos + 1]);
		}
	}
#endif

	pfree(fc_set);
}

/* ---
 * 向集合中添加事件。可能的事件有：
 * - WL_LATCH_SET：等待闸门被设置
 * - WL_POSTMASTER_DEATH：等待主进程死亡
 * - WL_SOCKET_READABLE：等待套接字变为可读，
 *	可以与其他 WL_SOCKET_* 事件合并为一个事件
 * - WL_SOCKET_WRITEABLE：等待套接字变为可写，
 *	可以与其他 WL_SOCKET_* 事件合并
 * - WL_SOCKET_CONNECTED：等待套接字连接建立，
 *	可以与其他 WL_SOCKET_* 事件合并（在非 Windows 平台上，这与 WL_SOCKET_WRITEABLE 相同）
 * - WL_SOCKET_CLOSED：等待套接字被远程对等体关闭。
 * - WL_EXIT_ON_PM_DEATH：如果主进程死亡立即退出
 *
 * 返回 WaitEventSet->events 中的偏移量（从 0 开始），可以用于使用 ModifyWaitEvent() 修改先前添加的等待事件。
 *
 * 在 WL_LATCH_SET 的情况下，闸门必须由当前进程拥有，
 * 即它必须是用 InitLatch 初始化的进程本地闸门，或通过调用 OwnLatch 关联到当前进程的共享闸门。
 *
 * 在 WL_SOCKET_READABLE/WRITEABLE/CONNECTED 的情况下，EOF 和错误
 * 条件会导致套接字被报告为可读/可写/连接，因此调用者可以处理该状态。
 *
 * 此处指定的 user_data 指针将被设置为 WaitEventSetWait() 返回的事件，
 * 允许轻松将附加数据与事件关联。
 */
int AddWaitEventToSet(WaitEventSet *fc_set, uint32 fc_events, pgsocket fc_fd, Latch *fc_latch,
				  void *fc_user_data)
{
	WaitEvent  *fc_event;

	/* 空间不足 */
	Assert(fc_set->nevents < fc_set->nevents_space);

	if (fc_events == WL_EXIT_ON_PM_DEATH)
	{
		fc_events = WL_POSTMASTER_DEATH;
		fc_set->exit_on_postmaster_death = true;
	}

	if (fc_latch)
	{
		if (fc_latch->owner_pid != MyProcPid)
			elog(ERROR, "cannot wait on a latch owned by another process");
		if (fc_set->latch)
			elog(ERROR, "cannot wait on more than one latch");
		if ((fc_events & WL_LATCH_SET) != WL_LATCH_SET)
			elog(ERROR, "latch events only support being set");
	}
	else
	{
		if (fc_events & WL_LATCH_SET)
			elog(ERROR, "cannot wait on latch without a specified latch");
	}

	/* 在没有套接字的情况下等待套接字就绪表示存在错误 */
	if (fc_fd == PGINVALID_SOCKET && (fc_events & WL_SOCKET_MASK))
		elog(ERROR, "cannot wait on socket event without a socket");

	fc_event = &fc_set->events[fc_set->nevents];
	fc_event->pos = fc_set->nevents++;
	fc_event->fd = fc_fd;
	fc_event->events = fc_events;
	fc_event->user_data = fc_user_data;
#ifdef WIN32
	fc_event->reset = false;
#endif

	if (fc_events == WL_LATCH_SET)
	{
		fc_set->latch = fc_latch;
		fc_set->latch_pos = fc_event->pos;
#if defined(WAIT_USE_SELF_PIPE)
		fc_event->fd = selfpipe_readfd;
#elif defined(WAIT_USE_SIGNALFD)
		fc_event->fd = signal_fd;
#else
		fc_event->fd = PGINVALID_SOCKET;
#ifdef WAIT_USE_EPOLL
		return fc_event->pos;
#endif
#endif
	}
	else if (fc_events == WL_POSTMASTER_DEATH)
	{
#ifndef WIN32
		fc_event->fd = postmaster_alive_fds[POSTMASTER_FD_WATCH];
#endif
	}

	/* 如有需要，执行特定于等待原语的初始化 */
#if defined(WAIT_USE_EPOLL)
	fc_WaitEventAdjustEpoll(fc_set, fc_event, EPOLL_CTL_ADD);
#elif defined(WAIT_USE_KQUEUE)
	fc_WaitEventAdjustKqueue(fc_set, fc_event, 0);
#elif defined(WAIT_USE_POLL)
	fc_WaitEventAdjustPoll(fc_set, fc_event);
#elif defined(WAIT_USE_WIN32)
	fc_WaitEventAdjustWin32(fc_set, fc_event);
#endif

	return fc_event->pos;
}

/*
 * 更改事件掩码，并在 WL_LATCH_SET 的情况下，更改与 WaitEvent 相关联的闸门。
 * 闸门可以更改为 NULL 以暂时禁用闸门，然后稍后再设置回某个闸门。
 *
 * 'pos' 是 AddWaitEventToSet 返回的 ID。
 */
void ModifyWaitEvent(WaitEventSet *fc_set, int fc_pos, uint32 fc_events, Latch *fc_latch)
{
	WaitEvent  *fc_event;
#if defined(WAIT_USE_KQUEUE)
	int			fc_old_events;
#endif

	Assert(fc_pos < fc_set->nevents);

	fc_event = &fc_set->events[fc_pos];
#if defined(WAIT_USE_KQUEUE)
	fc_old_events = fc_event->events;
#endif

	/*
	 * 如果事件掩码或关联的闸门都没有更改，则提前返回。
	 * 这是一个重要的优化，对于一些套接字，ModifyWaitEvent 经常被用来从等待读取切换到
	 * 等待写入。
	 */
	if (fc_events == fc_event->events &&
		(!(fc_event->events & WL_LATCH_SET) || fc_set->latch == fc_latch))
		return;

	if (fc_event->events & WL_LATCH_SET &&
		fc_events != fc_event->events)
	{
		elog(ERROR, "cannot modify latch event");
	}

	if (fc_event->events & WL_POSTMASTER_DEATH)
	{
		elog(ERROR, "cannot modify postmaster death event");
	}

	/* FIXME：验证事件掩码 */
	fc_event->events = fc_events;

	if (fc_events == WL_LATCH_SET)
	{
		if (fc_latch && fc_latch->owner_pid != MyProcPid)
			elog(ERROR, "cannot wait on a latch owned by another process");
		fc_set->latch = fc_latch;

		/*
		 * 在 Unix 上，我们不需要修改内核对象，因为
		 * 底层管道（如果存在）对于所有闸门是相同的，因此我们可以立即返回。在 Windows 上，我们需要更新我们的句柄数组，但我们将旧的保留在原处，并容忍在禁用闸门时出现虚假唤醒。
		 */
#if defined(WAIT_USE_WIN32)
		if (!fc_latch)
			return;
#else
		return;
#endif
	}

#if defined(WAIT_USE_EPOLL)
	fc_WaitEventAdjustEpoll(fc_set, fc_event, EPOLL_CTL_MOD);
#elif defined(WAIT_USE_KQUEUE)
	fc_WaitEventAdjustKqueue(fc_set, fc_event, fc_old_events);
#elif defined(WAIT_USE_POLL)
	fc_WaitEventAdjustPoll(fc_set, fc_event);
#elif defined(WAIT_USE_WIN32)
	fc_WaitEventAdjustWin32(fc_set, fc_event);
#endif
}

#if defined(WAIT_USE_EPOLL)
/*
 * action 可以是 EPOLL_CTL_ADD | EPOLL_CTL_MOD | EPOLL_CTL_DEL 中的一个
 */
static void fc_WaitEventAdjustEpoll(WaitEventSet *fc_set, WaitEvent *fc_event, int fc_action)
{
	struct epoll_event fc_epoll_ev;
	int			fc_rc;

	/* 指向我们事件的指针，由 epoll_wait 返回 */
	fc_epoll_ev.data.ptr = fc_event;
	/* 始终等待错误 */
	fc_epoll_ev.events = EPOLLERR | EPOLLHUP;

	/* 一次准备 pollfd 条目 */
	if (fc_event->events == WL_LATCH_SET)
	{
		Assert(fc_set->latch != NULL);
		fc_epoll_ev.events |= EPOLLIN;
	}
	else if (fc_event->events == WL_POSTMASTER_DEATH)
	{
		fc_epoll_ev.events |= EPOLLIN;
	}
	else
	{
		Assert(fc_event->fd != PGINVALID_SOCKET);
		Assert(fc_event->events & (WL_SOCKET_READABLE |
								WL_SOCKET_WRITEABLE |
								WL_SOCKET_CLOSED));

		if (fc_event->events & WL_SOCKET_READABLE)
			fc_epoll_ev.events |= EPOLLIN;
		if (fc_event->events & WL_SOCKET_WRITEABLE)
			fc_epoll_ev.events |= EPOLLOUT;
		if (fc_event->events & WL_SOCKET_CLOSED)
			fc_epoll_ev.events |= EPOLLRDHUP;
	}

	
/*
	 * 尽管未使用，但如果将 EPOLL_CTL_DEL 作为操作传递，我们仍然将 epoll_ev 作为数据参数传递。曾经有一个 epoll bug 需要这样做，实际上这使代码更简洁...
	 */
	fc_rc = epoll_ctl(fc_set->epoll_fd, fc_action, fc_event->fd, &fc_epoll_ev);

	if (fc_rc < 0)
		ereport(ERROR,
				(errcode_for_socket_access(),
				 errmsg("%s() failed: %m",
						"epoll_ctl")));
}
#endif

#if defined(WAIT_USE_POLL)
static void fc_WaitEventAdjustPoll(WaitEventSet *fc_set, WaitEvent *fc_event)
{
	struct pollfd *fc_pollfd = &fc_set->pollfds[fc_event->pos];

	fc_pollfd->revents = 0;
	fc_pollfd->fd = fc_event->fd;

	/* 一次准备 pollfd 条目 */
	if (fc_event->events == WL_LATCH_SET)
	{
		Assert(fc_set->latch != NULL);
		fc_pollfd->events = POLLIN;
	}
	else if (fc_event->events == WL_POSTMASTER_DEATH)
	{
		fc_pollfd->events = POLLIN;
	}
	else
	{
		Assert(fc_event->events & (WL_SOCKET_READABLE |
								WL_SOCKET_WRITEABLE |
								WL_SOCKET_CLOSED));
		fc_pollfd->events = 0;
		if (fc_event->events & WL_SOCKET_READABLE)
			fc_pollfd->events |= POLLIN;
		if (fc_event->events & WL_SOCKET_WRITEABLE)
			fc_pollfd->events |= POLLOUT;
#ifdef POLLRDHUP
		if (fc_event->events & WL_SOCKET_CLOSED)
			fc_pollfd->events |= POLLRDHUP;
#endif
	}

	Assert(fc_event->fd != PGINVALID_SOCKET);
}
#endif

#if defined(WAIT_USE_KQUEUE)

/*
 * 在大多数 BSD 家族系统中，struct kevent 的 udata 成员的类型为
 * void *，因此我们可以直接转换为/从 WaitEvent *。不幸的是，
 * NetBSD 将其定义为 intptr_t，因此在这里我们用 lvalue 强制转换来掩盖这种差异。
 */
#define AccessWaitEvent(k_ev) (*((WaitEvent **)(&(k_ev)->udata)))

static inline void fc_WaitEventAdjustKqueueAdd(struct kevent *fc_k_ev, int fc_filter, int fc_action,
						 WaitEvent *fc_event)
{
	fc_k_ev->ident = fc_event->fd;
	fc_k_ev->filter = fc_filter;
	fc_k_ev->flags = fc_action;
	fc_k_ev->fflags = 0;
	fc_k_ev->data = 0;
	AccessWaitEvent(fc_k_ev) = fc_event;
}

static inline void fc_WaitEventAdjustKqueueAddPostmaster(struct kevent *fc_k_ev, WaitEvent *fc_event)
{
	/* 目前 postmaster 死亡只能添加，不能移除。 */
	fc_k_ev->ident = PostmasterPid;
	fc_k_ev->filter = EVFILT_PROC;
	fc_k_ev->flags = EV_ADD;
	fc_k_ev->fflags = NOTE_EXIT;
	fc_k_ev->data = 0;
	AccessWaitEvent(fc_k_ev) = fc_event;
}

static inline void fc_WaitEventAdjustKqueueAddLatch(struct kevent *fc_k_ev, WaitEvent *fc_event)
{
	/* 目前 latch 只能添加，不能移除。 */
	fc_k_ev->ident = SIGURG;
	fc_k_ev->filter = EVFILT_SIGNAL;
	fc_k_ev->flags = EV_ADD;
	fc_k_ev->fflags = 0;
	fc_k_ev->data = 0;
	AccessWaitEvent(fc_k_ev) = fc_event;
}

/*
 * old_events 是之前的事件掩码，用于计算发生了什么变化。
 */
static void fc_WaitEventAdjustKqueue(WaitEventSet *fc_set, WaitEvent *fc_event, int fc_old_events)
{
	int			fc_rc;
	struct kevent fc_k_ev[2];
	int			fc_count = 0;
	bool		fc_new_filt_read = false;
	bool		fc_old_filt_read = false;
	bool		fc_new_filt_write = false;
	bool		fc_old_filt_write = false;

	if (fc_old_events == fc_event->events)
		return;

	Assert(fc_event->events != WL_LATCH_SET || fc_set->latch != NULL);
	Assert(fc_event->events == WL_LATCH_SET ||
		   fc_event->events == WL_POSTMASTER_DEATH ||
		   (fc_event->events & (WL_SOCKET_READABLE |
							 WL_SOCKET_WRITEABLE |
							 WL_SOCKET_CLOSED)));

	if (fc_event->events == WL_POSTMASTER_DEATH)
	{
		/*
		 * 与所有其他实现不同，我们使用进程通知来检测 postmaster 死亡，
		 * 而不是等待 postmaster 存活管道。
		 */
		fc_WaitEventAdjustKqueueAddPostmaster(&fc_k_ev[fc_count++], fc_event);
	}
	else if (fc_event->events == WL_LATCH_SET)
	{
		/* 我们使用信号事件检测锁存器唤醒。 */
		fc_WaitEventAdjustKqueueAddLatch(&fc_k_ev[fc_count++], fc_event);
	}
	else
	{
		/*
		 * 我们需要计算从旧事件掩码到新事件掩码所需的添加和删除，因为 kevent 将可读和可写视为独立事件。
		 */
		if (fc_old_events & (WL_SOCKET_READABLE | WL_SOCKET_CLOSED))
			fc_old_filt_read = true;
		if (fc_event->events & (WL_SOCKET_READABLE | WL_SOCKET_CLOSED))
			fc_new_filt_read = true;
		if (fc_old_events & WL_SOCKET_WRITEABLE)
			fc_old_filt_write = true;
		if (fc_event->events & WL_SOCKET_WRITEABLE)
			fc_new_filt_write = true;
		if (fc_old_filt_read && !fc_new_filt_read)
			fc_WaitEventAdjustKqueueAdd(&fc_k_ev[fc_count++], EVFILT_READ, EV_DELETE,
									 fc_event);
		else if (!fc_old_filt_read && fc_new_filt_read)
			fc_WaitEventAdjustKqueueAdd(&fc_k_ev[fc_count++], EVFILT_READ, EV_ADD,
									 fc_event);
		if (fc_old_filt_write && !fc_new_filt_write)
			fc_WaitEventAdjustKqueueAdd(&fc_k_ev[fc_count++], EVFILT_WRITE, EV_DELETE,
									 fc_event);
		else if (!fc_old_filt_write && fc_new_filt_write)
			fc_WaitEventAdjustKqueueAdd(&fc_k_ev[fc_count++], EVFILT_WRITE, EV_ADD,
									 fc_event);
	}

	/* 对于 WL_SOCKET_READ -> WL_SOCKET_CLOSED，不需要更改。 */
	if (fc_count == 0)
		return;

	Assert(fc_count <= 2);

	fc_rc = kevent(fc_set->kqueue_fd, &fc_k_ev[0], fc_count, NULL, 0, NULL);

	/*
	 * 在添加主进程的 pid 时，我们必须考虑它可能已经退出，甚至可能被另一个具有相同 pid 的进程替代。
	 * 如果是这样，我们必须推迟将其报告为事件，直到下一次调用 WaitEventSetWaitBlock()。
	 */

	if (fc_rc < 0)
	{
		if (fc_event->events == WL_POSTMASTER_DEATH &&
			(errno == ESRCH || errno == EACCES))
			fc_set->report_postmaster_not_running = true;
		else
			ereport(ERROR,
					(errcode_for_socket_access(),
					 errmsg("%s() failed: %m",
							"kevent")));
	}
	else if (fc_event->events == WL_POSTMASTER_DEATH &&
			 PostmasterPid != getppid() &&
			 !PostmasterIsAlive())
	{
		/*
		 * 额外的 PostmasterIsAliveInternal() 检查可以防止在被调试器跟踪时，系统对 getppid() 的值产生误报。
		 */
		fc_set->report_postmaster_not_running = true;
	}
}

#endif

#if defined(WAIT_USE_WIN32)
static void fc_WaitEventAdjustWin32(WaitEventSet *fc_set, WaitEvent *fc_event)
{
	HANDLE	   *fc_handle = &fc_set->handles[fc_event->pos + 1];

	if (fc_event->events == WL_LATCH_SET)
	{
		Assert(fc_set->latch != NULL);
		*fc_handle = fc_set->latch->event;
	}
	else if (fc_event->events == WL_POSTMASTER_DEATH)
	{
		*fc_handle = PostmasterHandle;
	}
	else
	{
		int			flags = FD_CLOSE;	/* 始终检查错误/EOF */

		if (fc_event->events & WL_SOCKET_READABLE)
			flags |= FD_READ;
		if (fc_event->events & WL_SOCKET_WRITEABLE)
			flags |= FD_WRITE;
		if (fc_event->events & WL_SOCKET_CONNECTED)
			flags |= FD_CONNECT;

		if (*fc_handle == WSA_INVALID_EVENT)
		{
			*fc_handle = WSACreateEvent();
			if (*fc_handle == WSA_INVALID_EVENT)
				elog(ERROR, "failed to create event for socket: error code %d",
					 WSAGetLastError());
		}
		if (WSAEventSelect(fc_event->fd, *fc_handle, flags) != 0)
			elog(ERROR, "failed to set up event for socket: error code %d",
				 WSAGetLastError());

		Assert(fc_event->fd != PGINVALID_SOCKET);
	}
}
#endif

/*
 * 等待添加到集合中的事件发生，或直到超时到达。
 * 最多返回 nevents 发生的事件。
 *
 * 如果 timeout = -1，阻塞直到发生事件；如果 0，检查套接字的就绪性，但不阻塞；如果 > 0，则最多阻塞 timeout 毫秒。
 *
 * 返回发生事件的数量，如果达到了超时，则返回 0。
 *
 * 返回的事件将具有 fd、pos、user_data 字段，设置为与注册事件关联的值。
 */
int WaitEventSetWait(WaitEventSet *fc_set, long fc_timeout,
				 WaitEvent *fc_occurred_events, int fc_nevents,
				 uint32 fc_wait_event_info)
{
	int			fc_returned_events = 0;
	instr_time	fc_start_time;
	instr_time	fc_cur_time;
	long		fc_cur_timeout = -1;

	Assert(fc_nevents > 0);

	/*
	 * 如果请求，初始化超时。我们必须记录当前时间，以便在中断时确定剩余的超时。
	 */
	if (fc_timeout >= 0)
	{
		INSTR_TIME_SET_CURRENT(fc_start_time);
		Assert(fc_timeout >= 0 && fc_timeout <= INT_MAX);
		fc_cur_timeout = fc_timeout;
	}

	pgstat_report_wait_start(fc_wait_event_info);

#ifndef WIN32
	waiting = true;
#else
	/* 确保即使锁存器已经设置，信号也能被处理 */
	pgwin32_dispatch_queued_signals();
#endif
	while (fc_returned_events == 0)
	{
		int			fc_rc;

		/*
		 * 检查锁存器是否已经设置。如果是，立即离开循环，避免再次阻塞。我们不尝试报告任何其他可能也已满足的事件。
		 *
		 * 如果在此和下面的 WaitEventSetWaitBlock() 之间有人设置了锁存器，设置者将向管道写入一个字节（或者信号我们，信号处理程序将执行该操作），就绪例程将立即返回。
		 *
		 * 在 unix 上，如果自管道中有一个待处理字节，我们将在阻塞时注意到。只有在这种情况下清除管道可以避免每次使用 WaitLatchOrSocket() 时都要排空它。如果管道缓冲区填满我们仍然好的，因为管道处于非阻塞模式。发生这种情况的可能性不大，因为自管道只有在我们阻塞（等待 = true），或从信号处理程序 latch_sigurg_handler() 内部填充。
		 *
		 * 在 windows 上，我们也会注意到在阻塞时锁存器是否有待处理事件，但没有任何填满的危险，因为“设置已设置的事件没有效果。”
		 *
		 * 注意：我们假设与锁存器管理相关的内核调用将在具有弱内存顺序的机器上提供足够的同步，以便我们不会错过看到 is_set，如果通知已经被排队。
		 */
		if (fc_set->latch && !fc_set->latch->is_set)
		{
			/* 即将进入锁存器睡眠 */
			fc_set->latch->maybe_sleeping = true;
			pg_memory_barrier();
			/* 重新检查 */
		}

		if (fc_set->latch && fc_set->latch->is_set)
		{
			fc_occurred_events->fd = PGINVALID_SOCKET;
			fc_occurred_events->pos = fc_set->latch_pos;
			fc_occurred_events->user_data =
				fc_set->events[fc_set->latch_pos].user_data;
			fc_occurred_events->events = WL_LATCH_SET;
			fc_occurred_events++;
			fc_returned_events++;

			/* 可能已经在上面设置 */
			fc_set->latch->maybe_sleeping = false;

			break;
		}

		/*
		 * 使用本文件顶部选择的就绪原语等待事件。如果返回 -1，则发生了超时，如果是 0，则我们必须重试，所有 >= 1 的值都是返回的事件数量。
		 */
		fc_rc = fc_WaitEventSetWaitBlock(fc_set, fc_cur_timeout,
								   fc_occurred_events, fc_nevents);

		if (fc_set->latch)
		{
			Assert(fc_set->latch->maybe_sleeping);
			fc_set->latch->maybe_sleeping = false;
		}

		if (fc_rc == -1)
			break;				/* 发生超时 */
		else
			fc_returned_events = fc_rc;

		/* 如果我们还没有完成，更新下次迭代的 cur_timeout */
		if (fc_returned_events == 0 && fc_timeout >= 0)
		{
			INSTR_TIME_SET_CURRENT(fc_cur_time);
			INSTR_TIME_SUBTRACT(fc_cur_time, fc_start_time);
			fc_cur_timeout = fc_timeout - (long) INSTR_TIME_GET_MILLISEC(fc_cur_time);
			if (fc_cur_timeout <= 0)
				break;
		}
	}
#ifndef WIN32
	waiting = false;
#endif

	pgstat_report_wait_end();

	return fc_returned_events;
}


#if defined(WAIT_USE_EPOLL)

/*
 * 使用 linux 的 epoll_wait(2) 等待。
 *
 * 这是首选的等待方法，因为可以交付多个就绪通知，而无需遍历所有 set->events。返回的 epoll_event 结构包含指向我们的事件的指针，使关联变得简单。
 */
static inline int fc_WaitEventSetWaitBlock(WaitEventSet *fc_set, int fc_cur_timeout,
					  WaitEvent *fc_occurred_events, int fc_nevents)
{
	int			fc_returned_events = 0;
	int			fc_rc;
	WaitEvent  *fc_cur_event;
	struct epoll_event *fc_cur_epoll_event;

	/* Sleep */
	fc_rc = epoll_wait(fc_set->epoll_fd, fc_set->epoll_ret_events,
					Min(fc_nevents, fc_set->nevents_space), fc_cur_timeout);

	/* 检查返回代码 */
	if (fc_rc < 0)
	{
		/* EINTR 是可以的，否则抱怨 */
		if (errno != EINTR)
		{
			waiting = false;
			ereport(ERROR,
					(errcode_for_socket_access(),
					 errmsg("%s() failed: %m",
							"epoll_wait")));
		}
		return 0;
	}
	else if (fc_rc == 0)
	{
		/* 超过超时 */
		return -1;
	}

	/*
	 * 至少有一个事件发生，迭代返回的 epoll 事件，直到它们全部处理，或者我们返回调用者所需的所有事件。
	 */
	for (fc_cur_epoll_event = fc_set->epoll_ret_events;
		 fc_cur_epoll_event < (fc_set->epoll_ret_events + fc_rc) &&
		 fc_returned_events < fc_nevents;
		 fc_cur_epoll_event++)
	{
		/* epoll 的数据指针设置为关联的 WaitEvent */
		fc_cur_event = (WaitEvent *) fc_cur_epoll_event->data.ptr;

		fc_occurred_events->pos = fc_cur_event->pos;
		fc_occurred_events->user_data = fc_cur_event->user_data;
		fc_occurred_events->events = 0;

		if (fc_cur_event->events == WL_LATCH_SET &&
			fc_cur_epoll_event->events & (EPOLLIN | EPOLLERR | EPOLLHUP))
		{
			/* 清空 signalfd。 */
			fc_drain();

			if (fc_set->latch && fc_set->latch->is_set)
			{
				fc_occurred_events->fd = PGINVALID_SOCKET;
				fc_occurred_events->events = WL_LATCH_SET;
				fc_occurred_events++;
				fc_returned_events++;
			}
		}
		else if (fc_cur_event->events == WL_POSTMASTER_DEATH &&
				 fc_cur_epoll_event->events & (EPOLLIN | EPOLLERR | EPOLLHUP))
		{
			/*
			 * 我们期望在远程端关闭时出现 EPOLLHUP，但由于我们不期望管道变得可读或有任何错误，因此也将这些情况视为主进程死亡。
			 *
			 * 对错误事件标记主进程为死亡保持警惕。关于这种情况发生的报告已经提出，包括使用旧原语（具体地说是 select(2)），而错误的 WL_POSTMASTER_DEATH 事件将是痛苦的。重新检查不会浪费太多成本。
			 */
			if (!PostmasterIsAliveInternal())
			{
				if (fc_set->exit_on_postmaster_death)
					proc_exit(1);
				fc_occurred_events->fd = PGINVALID_SOCKET;
				fc_occurred_events->events = WL_POSTMASTER_DEATH;
				fc_occurred_events++;
				fc_returned_events++;
			}
		}
		else if (fc_cur_event->events & (WL_SOCKET_READABLE |
									  WL_SOCKET_WRITEABLE |
									  WL_SOCKET_CLOSED))
		{
			Assert(fc_cur_event->fd != PGINVALID_SOCKET);

			if ((fc_cur_event->events & WL_SOCKET_READABLE) &&
				(fc_cur_epoll_event->events & (EPOLLIN | EPOLLERR | EPOLLHUP)))
			{
				/* 套接字中有可用数据，或 EOF */
				fc_occurred_events->events |= WL_SOCKET_READABLE;
			}

			if ((fc_cur_event->events & WL_SOCKET_WRITEABLE) &&
				(fc_cur_epoll_event->events & (EPOLLOUT | EPOLLERR | EPOLLHUP)))
			{
				/* 可写，或 EOF */
				fc_occurred_events->events |= WL_SOCKET_WRITEABLE;
			}

			if ((fc_cur_event->events & WL_SOCKET_CLOSED) &&
				(fc_cur_epoll_event->events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP)))
			{
				/* 远程对等方关闭，或错误 */
				fc_occurred_events->events |= WL_SOCKET_CLOSED;
			}

			if (fc_occurred_events->events != 0)
			{
				fc_occurred_events->fd = fc_cur_event->fd;
				fc_occurred_events++;
				fc_returned_events++;
			}
		}
	}

	return fc_returned_events;
}

#elif defined(WAIT_USE_KQUEUE)


/*
 * 在 BSD 系统和 macOS 上使用 kevent(2) 等待。
 *
 * 目前这与 epoll 代码相似，但将来它可以在用于等待的同一调用中修改 fd
 * 集，而不是通过单独的系统调用来执行。
 */
static int fc_WaitEventSetWaitBlock(WaitEventSet *fc_set, int fc_cur_timeout,
					  WaitEvent *fc_occurred_events, int fc_nevents)
{
	int			fc_returned_events = 0;
	int			fc_rc;
	WaitEvent  *fc_cur_event;
	struct kevent *fc_cur_kqueue_event;
	struct timespec fc_timeout;
	struct timespec *fc_timeout_p;

	if (fc_cur_timeout < 0)
		fc_timeout_p = NULL;
	else
	{
		fc_timeout.tv_sec = fc_cur_timeout / 1000;
		fc_timeout.tv_nsec = (fc_cur_timeout % 1000) * 1000000;
		fc_timeout_p = &fc_timeout;
	}

	/*
	 * 报告由 WaitEventAdjustKqueue() 或之前的 WaitEventSetWait() 调用发现的后端事件。
	 */
	if (unlikely(fc_set->report_postmaster_not_running))
	{
		if (fc_set->exit_on_postmaster_death)
			proc_exit(1);
		fc_occurred_events->fd = PGINVALID_SOCKET;
		fc_occurred_events->events = WL_POSTMASTER_DEATH;
		return 1;
	}

	/* Sleep */
	fc_rc = kevent(fc_set->kqueue_fd, NULL, 0,
				fc_set->kqueue_ret_events,
				Min(fc_nevents, fc_set->nevents_space),
				fc_timeout_p);

	/* 检查返回代码 */
	if (fc_rc < 0)
	{
		/* EINTR 是可以的，否则抱怨 */
		if (errno != EINTR)
		{
			waiting = false;
			ereport(ERROR,
					(errcode_for_socket_access(),
					 errmsg("%s() failed: %m",
							"kevent")));
		}
		return 0;
	}
	else if (fc_rc == 0)
	{
		/* 超过超时 */
		return -1;
	}

	/*
	 * 至少发生了一项事件，逐一处理返回的 kqueue 事件，
	 * 直到它们全部被处理，或者我们已返回调用者所需的所有事件。
	 */
	for (fc_cur_kqueue_event = fc_set->kqueue_ret_events;
		 fc_cur_kqueue_event < (fc_set->kqueue_ret_events + fc_rc) &&
		 fc_returned_events < fc_nevents;
		 fc_cur_kqueue_event++)
	{
		/* kevent的udata指向关联的WaitEvent */
		fc_cur_event = AccessWaitEvent(fc_cur_kqueue_event);

		fc_occurred_events->pos = fc_cur_event->pos;
		fc_occurred_events->user_data = fc_cur_event->user_data;
		fc_occurred_events->events = 0;

		if (fc_cur_event->events == WL_LATCH_SET &&
			fc_cur_kqueue_event->filter == EVFILT_SIGNAL)
		{
			if (fc_set->latch && fc_set->latch->is_set)
			{
				fc_occurred_events->fd = PGINVALID_SOCKET;
				fc_occurred_events->events = WL_LATCH_SET;
				fc_occurred_events++;
				fc_returned_events++;
			}
		}
		else if (fc_cur_event->events == WL_POSTMASTER_DEATH &&
				 fc_cur_kqueue_event->filter == EVFILT_PROC &&
				 (fc_cur_kqueue_event->fflags & NOTE_EXIT) != 0)
		{
			/*
			 * 内核只会在postmaster退出时告诉这个kqueue对象一次，
			 * 所以我们要记住这个，以便下次提供水平触发语义。
			 */
			fc_set->report_postmaster_not_running = true;

			if (fc_set->exit_on_postmaster_death)
				proc_exit(1);
			fc_occurred_events->fd = PGINVALID_SOCKET;
			fc_occurred_events->events = WL_POSTMASTER_DEATH;
			fc_occurred_events++;
			fc_returned_events++;
		}
		else if (fc_cur_event->events & (WL_SOCKET_READABLE |
									  WL_SOCKET_WRITEABLE |
									  WL_SOCKET_CLOSED))
		{
			Assert(fc_cur_event->fd >= 0);

			if ((fc_cur_event->events & WL_SOCKET_READABLE) &&
				(fc_cur_kqueue_event->filter == EVFILT_READ))
			{
				/* 可读，或EOF */
				fc_occurred_events->events |= WL_SOCKET_READABLE;
			}

			if ((fc_cur_event->events & WL_SOCKET_CLOSED) &&
				(fc_cur_kqueue_event->filter == EVFILT_READ) &&
				(fc_cur_kqueue_event->flags & EV_EOF))
			{
				/* 远程对等方已关闭 */
				fc_occurred_events->events |= WL_SOCKET_CLOSED;
			}

			if ((fc_cur_event->events & WL_SOCKET_WRITEABLE) &&
				(fc_cur_kqueue_event->filter == EVFILT_WRITE))
			{
				/* 可写，或 EOF */
				fc_occurred_events->events |= WL_SOCKET_WRITEABLE;
			}

			if (fc_occurred_events->events != 0)
			{
				fc_occurred_events->fd = fc_cur_event->fd;
				fc_occurred_events++;
				fc_returned_events++;
			}
		}
	}

	return fc_returned_events;
}

#elif defined(WAIT_USE_POLL)

/*
 * 使用poll(2)进行等待。
 *
 * 这允许一次接收多个事件的就绪通知，
 * 但需要遍历所有的set->pollfds。
 */
static inline int fc_WaitEventSetWaitBlock(WaitEventSet *fc_set, int fc_cur_timeout,
					  WaitEvent *fc_occurred_events, int fc_nevents)
{
	int			fc_returned_events = 0;
	int			fc_rc;
	WaitEvent  *fc_cur_event;
	struct pollfd *fc_cur_pollfd;

	/* Sleep */
	fc_rc = poll(fc_set->pollfds, fc_set->nevents, (int) fc_cur_timeout);

	/* 检查返回代码 */
	if (fc_rc < 0)
	{
		/* EINTR 是可以的，否则抱怨 */
		if (errno != EINTR)
		{
			waiting = false;
			ereport(ERROR,
					(errcode_for_socket_access(),
					 errmsg("%s() failed: %m",
							"poll")));
		}
		return 0;
	}
	else if (fc_rc == 0)
	{
		/* 超过超时 */
		return -1;
	}

	for (fc_cur_event = fc_set->events, fc_cur_pollfd = fc_set->pollfds;
		 fc_cur_event < (fc_set->events + fc_set->nevents) &&
		 fc_returned_events < fc_nevents;
		 fc_cur_event++, fc_cur_pollfd++)
	{
		/* 此FD没有活动，跳过 */
		if (fc_cur_pollfd->revents == 0)
			continue;

		fc_occurred_events->pos = fc_cur_event->pos;
		fc_occurred_events->user_data = fc_cur_event->user_data;
		fc_occurred_events->events = 0;

		if (fc_cur_event->events == WL_LATCH_SET &&
			(fc_cur_pollfd->revents & (POLLIN | POLLHUP | POLLERR | POLLNVAL)))
		{
			/* 自管道中有数据，清除它。 */
			fc_drain();

			if (fc_set->latch && fc_set->latch->is_set)
			{
				fc_occurred_events->fd = PGINVALID_SOCKET;
				fc_occurred_events->events = WL_LATCH_SET;
				fc_occurred_events++;
				fc_returned_events++;
			}
		}
		else if (fc_cur_event->events == WL_POSTMASTER_DEATH &&
				 (fc_cur_pollfd->revents & (POLLIN | POLLHUP | POLLERR | POLLNVAL)))
		{
			/*
			 * 我们期望在远程端关闭时获得一个POLLHUP，但因为
			 * 我们不期望管道变得可读或出现任何
			 * 错误，所以也将这些情况视为postmaster死亡。
			 *
			 * 要对信号postmaster死亡的错误事件保持警惕。
			 * 已经有报告称老旧原语（特别是select(2)）会发生这种情况，
			 * 一个虚假的WL_POSTMASTER_DEATH事件将是痛苦的。重新检查并不
			 * 费事。
			 */
			if (!PostmasterIsAliveInternal())
			{
				if (fc_set->exit_on_postmaster_death)
					proc_exit(1);
				fc_occurred_events->fd = PGINVALID_SOCKET;
				fc_occurred_events->events = WL_POSTMASTER_DEATH;
				fc_occurred_events++;
				fc_returned_events++;
			}
		}
		else if (fc_cur_event->events & (WL_SOCKET_READABLE |
									  WL_SOCKET_WRITEABLE |
									  WL_SOCKET_CLOSED))
		{
			int			fc_errflags = POLLHUP | POLLERR | POLLNVAL;

			Assert(fc_cur_event->fd >= PGINVALID_SOCKET);

			if ((fc_cur_event->events & WL_SOCKET_READABLE) &&
				(fc_cur_pollfd->revents & (POLLIN | fc_errflags)))
			{
				/* 套接字中有可用数据，或 EOF */
				fc_occurred_events->events |= WL_SOCKET_READABLE;
			}

			if ((fc_cur_event->events & WL_SOCKET_WRITEABLE) &&
				(fc_cur_pollfd->revents & (POLLOUT | fc_errflags)))
			{
				/* 可写，或EOF */
				fc_occurred_events->events |= WL_SOCKET_WRITEABLE;
			}

#ifdef POLLRDHUP
			if ((fc_cur_event->events & WL_SOCKET_CLOSED) &&
				(fc_cur_pollfd->revents & (POLLRDHUP | fc_errflags)))
			{
				/* 远程对等方关闭，或错误 */
				fc_occurred_events->events |= WL_SOCKET_CLOSED;
			}
#endif

			if (fc_occurred_events->events != 0)
			{
				fc_occurred_events->fd = fc_cur_event->fd;
				fc_occurred_events++;
				fc_returned_events++;
			}
		}
	}
	return fc_returned_events;
}

#elif defined(WAIT_USE_WIN32)

/*
 * 使用Windows的WaitForMultipleObjects()进行等待。
 *
 * 不幸的是，这将一次只返回一个就绪通知。请注意，尽管
 * WaitForMultipleObjects的官方文档在多个事件“被消费”时是模糊的，
 * https://blogs.msdn.microsoft.com/oldnewthing/20150409-00/?p=44273确认
 * 只有一个事件被“消费”。
 */
static inline int fc_WaitEventSetWaitBlock(WaitEventSet *fc_set, int fc_cur_timeout,
					  WaitEvent *fc_occurred_events, int fc_nevents)
{
	int			fc_returned_events = 0;
	DWORD		fc_rc;
	WaitEvent  *fc_cur_event;

	/* 重置任何需要的等待事件 */
	for (fc_cur_event = fc_set->events;
		 fc_cur_event < (fc_set->events + fc_set->nevents);
		 fc_cur_event++)
	{
		if (fc_cur_event->reset)
		{
			fc_WaitEventAdjustWin32(fc_set, fc_cur_event);
			fc_cur_event->reset = false;
		}

		/*
		 * 我们为每个WaitEventSet将套接字与一个新事件句柄关联。
		 * 如果另一端正常关闭，FD_CLOSE只会产生一次。
		 * 因此，如果在我们停止等待它后，它已经交付给另一个事件，
		 * 我们可能会错过FD_CLOSE通知。通过在设置
		 * 此句柄以接收通知后及进入睡眠前检查EOF来消除此竞争。
		 *
		 * XXX 如果我们在套接字的整个生命周期中都有一个事件句柄，
		 * 我们将不需要这样做。
		 */
		if (fc_cur_event->events & WL_SOCKET_READABLE)
		{
			char		fc_c;
			WSABUF		fc_buf;
			DWORD		fc_received;
			DWORD		fc_flags;

			fc_buf.buf = &fc_c;
			fc_buf.len = 1;
			fc_flags = MSG_PEEK;
			if (WSARecv(fc_cur_event->fd, &fc_buf, 1, &fc_received, &fc_flags, NULL, NULL) == 0)
			{
				fc_occurred_events->pos = fc_cur_event->pos;
				fc_occurred_events->user_data = fc_cur_event->user_data;
				fc_occurred_events->events = WL_SOCKET_READABLE;
				fc_occurred_events->fd = fc_cur_event->fd;
				return 1;
			}
		}

		/*
		 * Windows不保证记录一个FD_WRITE网络事件
		 * 表示可以发送更多数据，除非前一个send()失败并返回WSAEWOULDBLOCK。
		 * 虽然我们的调用者可能确实进行了这样的调用，
		 * 但我们在这里不能假设。因此，如果等待
		 * 可写状态，通过执行一个虚拟send()来强制。 如果虚拟
		 * send()成功，假设套接字确实处于可写状态，并立即返回。
		 * 此外，如果它返回的错误不是WSAEWOULDBLOCK，则返回可写状态
		 * 指示，以便让我们的调用者处理错误条件。
		 */
		if (fc_cur_event->events & WL_SOCKET_WRITEABLE)
		{
			char		fc_c;
			WSABUF		fc_buf;
			DWORD		fc_sent;
			int			fc_r;

			fc_buf.buf = &fc_c;
			fc_buf.len = 0;

			fc_r = WSASend(fc_cur_event->fd, &fc_buf, 1, &fc_sent, 0, NULL, NULL);
			if (fc_r == 0 || WSAGetLastError() != WSAEWOULDBLOCK)
			{
				fc_occurred_events->pos = fc_cur_event->pos;
				fc_occurred_events->user_data = fc_cur_event->user_data;
				fc_occurred_events->events = WL_SOCKET_WRITEABLE;
				fc_occurred_events->fd = fc_cur_event->fd;
				return 1;
			}
		}
	}

	/*
	 * 睡眠。
	 *
	 * 需要等待->nevents + 1，因为信号处理器在[0]中。
	 */
	fc_rc = WaitForMultipleObjects(fc_set->nevents + 1, fc_set->handles, FALSE,
								fc_cur_timeout);

	/* 检查返回代码 */
	if (fc_rc == WAIT_FAILED)
		elog(ERROR, "WaitForMultipleObjects() failed: error code %lu",
			 GetLastError());
	else if (fc_rc == WAIT_TIMEOUT)
	{
		/* 超过超时 */
		return -1;
	}

	if (fc_rc == WAIT_OBJECT_0)
	{
		/* 处理新到来的信号 */
		pgwin32_dispatch_queued_signals();
		return 0;				/* 重试 */
	}

	/*
	 * 由于始终存在的pgwin32_signal_event，有一个偏移量为1，
	 * 句柄偏移量直接对应于一个等待事件。
	 */
	fc_cur_event = (WaitEvent *) &fc_set->events[fc_rc - WAIT_OBJECT_0 - 1];

	fc_occurred_events->pos = fc_cur_event->pos;
	fc_occurred_events->user_data = fc_cur_event->user_data;
	fc_occurred_events->events = 0;

	if (fc_cur_event->events == WL_LATCH_SET)
	{
		/*
		 * 如果我们现在没有等待这个闩锁，
		 * 我们无法使用set->latch->event来重置触发的事件。
		 */
		if (!ResetEvent(fc_set->handles[fc_cur_event->pos + 1]))
			elog(ERROR, "ResetEvent failed: error code %lu", GetLastError());

		if (fc_set->latch && fc_set->latch->is_set)
		{
			fc_occurred_events->fd = PGINVALID_SOCKET;
			fc_occurred_events->events = WL_LATCH_SET;
			fc_occurred_events++;
			fc_returned_events++;
		}
	}
	else if (fc_cur_event->events == WL_POSTMASTER_DEATH)
	{
		/*
		 * postmaster显然已死。由于错误地返回WL_POSTMASTER_DEATH的后果
		 * 可能相当不愉快，我们特别验证这一点，
		 * 即使没有已知原因认为该事件在Windows上可能被错误设置。
		 */
		if (!PostmasterIsAliveInternal())
		{
			if (fc_set->exit_on_postmaster_death)
				proc_exit(1);
			fc_occurred_events->fd = PGINVALID_SOCKET;
			fc_occurred_events->events = WL_POSTMASTER_DEATH;
			fc_occurred_events++;
			fc_returned_events++;
		}
	}
	else if (fc_cur_event->events & WL_SOCKET_MASK)
	{
		WSANETWORKEVENTS fc_resEvents;
		HANDLE		fc_handle = fc_set->handles[fc_cur_event->pos + 1];

		Assert(fc_cur_event->fd);

		fc_occurred_events->fd = fc_cur_event->fd;

		ZeroMemory(&fc_resEvents, sizeof(fc_resEvents));
		if (WSAEnumNetworkEvents(fc_cur_event->fd, fc_handle, &fc_resEvents) != 0)
			elog(ERROR, "failed to enumerate network events: error code %d",
				 WSAGetLastError());
		if ((fc_cur_event->events & WL_SOCKET_READABLE) &&
			(fc_resEvents.lNetworkEvents & FD_READ))
		{
			/* 套接字中有可用数据 */
			fc_occurred_events->events |= WL_SOCKET_READABLE;

			/*------
			 * WaitForMultipleObjects不保证如果闩锁同时设置，则将返回读取事件。
			 * 即使返回，调用者也可能会丢弃该事件，期待它在下次调用中再次发生。
			 * 因此，如果此WaitEventSet再次使用，我们必须强制重置事件，
			 * 以避免无限挂起。有关套接字事件的行为，请参阅
			 * https://msdn.microsoft.com/en-us/library/windows/desktop/ms741576(v=vs.85).aspx
			 *------
			 */
			fc_cur_event->reset = true;
		}
		if ((fc_cur_event->events & WL_SOCKET_WRITEABLE) &&
			(fc_resEvents.lNetworkEvents & FD_WRITE))
		{
			/* 可写 */
			fc_occurred_events->events |= WL_SOCKET_WRITEABLE;
		}
		if ((fc_cur_event->events & WL_SOCKET_CONNECTED) &&
			(fc_resEvents.lNetworkEvents & FD_CONNECT))
		{
			/* 已连接 */
			fc_occurred_events->events |= WL_SOCKET_CONNECTED;
		}
		if (fc_resEvents.lNetworkEvents & FD_CLOSE)
		{
			/* EOF/错误，因此信号所有调用者请求的套接字标志 */
			fc_occurred_events->events |= (fc_cur_event->events & WL_SOCKET_MASK);
		}

		if (fc_occurred_events->events != 0)
		{
			fc_occurred_events++;
			fc_returned_events++;
		}
	}

	return fc_returned_events;
}
#endif

/*
 * 返回当前构建选项是否可以报告WL_SOCKET_CLOSED。
 */
bool WaitEventSetCanReportClosed(void)
{
#if (defined(WAIT_USE_POLL) && defined(POLLRDHUP)) || \
	defined(WAIT_USE_EPOLL) || \
	defined(WAIT_USE_KQUEUE)
	return true;
#else
	return false;
#endif
}

/*
 * 获取在给定WaitEventSet中注册的等待事件的数量。
 */
int GetNumRegisteredWaitEvents(WaitEventSet *fc_set)
{
	return fc_set->nevents;
}

#if defined(WAIT_USE_SELF_PIPE)

/*
 * SetLatch使用SIGURG唤醒等待闩锁的进程。
 *
 * 如果我们在等待，则唤醒WaitLatch。
 */
static void fc_latch_sigurg_handler(SIGNAL_ARGS)
{
	int			fc_save_errno = errno;

	if (waiting)
		fc_sendSelfPipeByte();

	errno = fc_save_errno;
}

/* 向自管道发送一个字节，以唤醒WaitLatch */
static void fc_sendSelfPipeByte(void)
{
	int			fc_rc;
	char		fc_dummy = 0;

retry:
	fc_rc = write(selfpipe_writefd, &fc_dummy, 1);
	if (fc_rc < 0)
	{
		/* 如果被信号中断，则重试 */
		if (errno == EINTR)
			goto retry;

		/*
		 * 如果管道已满，我们不需要重试，那里已经存在的数据足以唤醒 WaitLatch。
		 */
		if (errno == EAGAIN || errno == EWOULDBLOCK)
			return;

		/*
		 * 哎呀，write()因为其他原因失败了。我们可能处于信号处理程序中，因此调用elog()并不安全。我们别无选择，只能默默忽略这个错误。
		 */
		return;
	}
}

#endif

#if defined(WAIT_USE_SELF_PIPE) || defined(WAIT_USE_SIGNALFD)

/*
 * 从自管道或signalfd中读取所有可用数据。
 *
 * 注意：只有在等待 = true 时才调用此函数。如果失败且没有
 * 返回，必须先重置该标志（尽管理想情况下，这种情况永远不会发生）。
 */
static void fc_drain(void)
{
	char		fc_buf[1024];
	int			fc_rc;
	int			fc_fd;

#ifdef WAIT_USE_SELF_PIPE
	fc_fd = selfpipe_readfd;
#else
	fc_fd = signal_fd;
#endif

	for (;;)
	{
		fc_rc = read(fc_fd, fc_buf, sizeof(fc_buf));
		if (fc_rc < 0)
		{
			if (errno == EAGAIN || errno == EWOULDBLOCK)
				break;			/* 描述符为空 */
			else if (errno == EINTR)
				continue;		/* 重试 */
			else
			{
				waiting = false;
#ifdef WAIT_USE_SELF_PIPE
				elog(ERROR, "read() on self-pipe failed: %m");
#else
				elog(ERROR, "read() on signalfd failed: %m");
#endif
			}
		}
		else if (fc_rc == 0)
		{
			waiting = false;
#ifdef WAIT_USE_SELF_PIPE
			elog(ERROR, "unexpected EOF on self-pipe");
#else
			elog(ERROR, "unexpected EOF on signalfd");
#endif
		}
		else if (fc_rc < sizeof(fc_buf))
		{
			/* 我们成功地排空了管道；不需要再次读取() */
			break;
		}
		/* 否则缓冲区大小不足，因此再次读取 */
	}
}

#endif
